
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Tabla de simbolos.
 * @author paco
 */
public class TablaSimbolos
{

	/**
	 * Contador de variables locales.
	 */
	private int localPos;

	/**
	 * Lista de simbolos que contiene la tabla.
	 */
	private HashMap<String, Simbolo> simbolos;
	
	/**
	 * Tabla padre ( contiene a la tabla actual ).
	 */
	private TablaSimbolos padre;
	/**
	 * Traducción de la tabla actual, incluye las traducciones de todas las tablas hijo.
	 */
	private StringBuilder localsValue;

	/**
	 * Constructor de clase.
	 */
	public TablaSimbolos()
	{
		simbolos = new HashMap<String, Simbolo>();
		padre = null;
		localPos = 0;
		localsValue = new StringBuilder();
	}

	/**
	 * Constructor.
	 * @param padre Tabla padre. 
	 */
	public TablaSimbolos(TablaSimbolos padre)
	{
		this.padre = padre;
		simbolos = new HashMap<String, Simbolo>();
		this.localPos = padre.localPos;
		localsValue = padre.localsValue;
	}

	/**
	 * Obtiene la tabla padre de una tabla.
	 * @return Tabla padre.
	 */
	public TablaSimbolos getPadre()
	{
		return padre;
	}

	/**
	 * Obtiene la traducción de la tabla.
	 * @return Traducción de la tabla.
	 */
	public String getLocalsValue()
	{
		return localsValue.toString();
	}

	/**
	 * Obtiene un simbolo de la tabla.
	 * @return Lista de simbolos de la tabla.
	 */
	public Simbolo getSimbolo(String name)
	{
		Simbolo s = simbolos.get(name);

		if (s != null)
		{
			return s;
		} 
		else
		{
			return padre != null ? padre.getSimbolo(name) : null;
		}
	}

	/**
	 * Cambia la tabla padre.
	 * @param padre Nueva tabla padre.
	 */
	public void setPadre(TablaSimbolos padre)
	{
		this.padre = padre;
	}

	/**
	 * Cambia los simbolos de uan tabla.
	 * @param simbolos Nuevos simbolos.
	 */
	public void setSimbolos(HashMap<String, Simbolo> simbolos)
	{
		this.simbolos = simbolos;
	}

	public int getLocalPos()
	{
		return localPos;
	}

	public void setLocalPos(int localPos)
	{
		this.localPos = localPos;
	}
	
	/**
	 * Cambia la traducción
	 * @param traduccion Nueva traducción.
	 */
	public void setTraduccion(String traduccion)
	{
		this.localsValue.append(traduccion);
	}

	/**
	 * Comprueba si existe un simbolo en todos los ambitos.
	 * @param s Simbolo a buscar.
	 * @return Cierto si se encuentra el simbolo y falso en caso contrario.
	 */
	public boolean existe(Simbolo s)
	{
		if ( this.getSimbolo(s.nombre) == null )
		{
			return ( padre == null ? false : padre.existe(s) );
		}
		return true;
	}

	/**
	 * Comprueba si existe en simbolo en el ámbito actual.
	 * @param s Simbolo a comprobar.
	 * @return Cierto si existe el simbolo en el ambito y falso en caso contrario.
	 */
	public boolean existeEnAmbito(Simbolo s)
	{
		if ( simbolos.get( s.getNombre() ) == null )
		{
			return false;
		} 
		else
		{
			return true;
		}
	}

	/**
	 * Añade un nuevo simbolo a la tabla.
	 * @param s Simbolo a instertar.
	 * @return Devuelve true si se ha podido insertar el simbolo y false si ya existia.
	 */
	public boolean addSimbolo(Simbolo s)
	{
		if (this.existeEnAmbito(s))
		{
			return false;
		}
		else
		{
			if ( !s.isFuncion() && !s.isClase() && !s.isExcepcion() )
			{
				s.setLocalPos(localPos);
				if ( localsValue.length() != 0 )
				{
					localsValue.append(",");
				}
				localsValue.append(s.getLocals());
				localPos++;
			}
			simbolos.put(s.getNombre(), s);
			return true;
		}
	}

	/**
	 * Sube un ambito en la tabla de simbolos y copia su traducción al ámbito superior.
	 * @return Tabla de simbolos correspondiente al ambito superior.
	 */
	public TablaSimbolos pop()
	{
		if (padre != null)
		{
			if ( padre.localsValue.length() != 0 && this.localsValue.length() > 0 )
			{
				padre.localsValue.append(",");
			}
			padre.localsValue.append(this.localsValue.toString());
			padre.setLocalPos(localPos);
		}
		return (this.padre);
	}

	/**
	 * Añade un nuevo ambito a la tabla se simbolos.
	 * @return  Nuevo ambito de la tabla de simbolos.
	 */
	public TablaSimbolos push()
	{
		TablaSimbolos tablaNueva = new TablaSimbolos();
		tablaNueva.padre = this;
		tablaNueva.setLocalPos(this.localPos);
		return tablaNueva;
	}

	/**
	 * Muestra por pantalla el contenido de una tabla de simbolos.
	 * @return Traducción de la tabla de simbolos.
	 */
	@Override
	public String toString()
	{
		StringBuilder trad = new StringBuilder();
		
		if ( simbolos.isEmpty() )
		{
			return "VACIAAAAAAAAAAAAA\n";
		}
		else
		{
			Iterator it = simbolos.entrySet().iterator();

			while(it.hasNext())
			{
				Map.Entry e = (Map.Entry) it.next();
				trad.append(e.getValue().toString());
			}
		}
		
		return trad.toString();
	}
	
	/**
	 * Muestra el contenido de una tabla de simbolos.
	 * @return Traducción de la tabla de simbolos.
	 */
	public String toString(String tabulacion)
	{
		StringBuilder trad = new StringBuilder();
		
		Iterator it = simbolos.entrySet().iterator();

		while(it.hasNext())
		{
			Map.Entry e = (Map.Entry) it.next();
			Simbolo s = (Simbolo)e.getValue();
			trad.append(s.toString(tabulacion));
		}
		
		return trad.toString();
	}
	
	/**
	 * Muestra el .locals de un ámbito de la tabla de simbolos.
	 * @return .locals
	 */
	public String getLocals(String tabulacion)
	{
		StringBuilder trad = new StringBuilder();
		
		Iterator it = simbolos.entrySet().iterator();

		while(it.hasNext())
		{
			Map.Entry e = (Map.Entry) it.next();
			Simbolo s = (Simbolo)e.getValue();
			trad.append(s.getLocals(tabulacion));
		}
		
		return trad.toString();
	}
}
